package io.noties.markwon.editor;


import com.noties.markwon.annotation.NonNull;
import com.noties.markwon.annotation.Nullable;
import com.noties.markwon.wrapper.text.Editable;
import com.noties.markwon.wrapper.text.SpannableStringBuilder;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import ohos.agp.components.Component;
import ohos.agp.components.Text;
import ohos.agp.components.TextField;

/**
 * Implementation of TextWatcher that uses {@link MarkwonEditor#process(Editable)} method
 * to apply markdown highlighting right after text changes.
 *
 * @see MarkwonEditor#process(Editable)
 * @see MarkwonEditor#preRender(Editable, MarkwonEditor.PreRenderResultListener)
 * @see #withProcess(MarkwonEditor)
 * @see #withPreRender(MarkwonEditor, ExecutorService, TextField)
 * @since 4.2.0
 */
public abstract class MarkwonEditorTextWatcher implements Text.TextObserver {

    @NonNull
    public static MarkwonEditorTextWatcher withProcess(@NonNull MarkwonEditor editor) {
        return new WithProcess(editor);
    }

    @NonNull
    public static MarkwonEditorTextWatcher withPreRender(
            @NonNull MarkwonEditor editor,
            @NonNull ExecutorService executorService,
            @NonNull TextField editText) {
        return new WithPreRender(editor, executorService, editText);
    }

    public abstract void afterTextChanged(Editable s);

    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }


    static class WithProcess extends MarkwonEditorTextWatcher {

        private final MarkwonEditor editor;

        private boolean selfChange;

        WithProcess(@NonNull MarkwonEditor editor) {
            this.editor = editor;
        }

        @Override
        public void afterTextChanged(Editable s) {

            if (selfChange) {
                return;
            }

            selfChange = true;
            try {
                editor.process(s);
            } finally {
                selfChange = false;
            }
        }

        @Override
        public void onTextUpdated(String s, int i, int i1, int i2) {

        }
    }

    static class WithPreRender extends MarkwonEditorTextWatcher {

        private final MarkwonEditor editor;
        private final ExecutorService executorService;

        // As we operate on a single thread (main) we are fine with a regular int
        //  for marking current _generation_
        private int generator;

        @Nullable
        private TextField editText;

        private Future<?> future;

        private boolean selfChange;

        WithPreRender(
                @NonNull MarkwonEditor editor,
                @NonNull ExecutorService executorService,
                @NonNull TextField editText) {
            this.editor = editor;
            this.executorService = executorService;
            this.editText = editText;
            this.editText.setBindStateChangedListener(new Component.BindStateChangedListener() {
                @Override
                public void onComponentBoundToWindow(Component component) {

                }

                @Override
                public void onComponentUnboundFromWindow(Component component) {
                    WithPreRender.this.editText = null;
                }
            });
        }

        @Override
        public void afterTextChanged(Editable s) {

            if (selfChange) {
                return;
            }

            // both will be the same here (generator incremented and key assigned incremented value)
            final int key = ++this.generator;

            if (future != null) {
                future.cancel(true);
            }

            // copy current content (it's not good to pass TextField editable to other thread)
            final SpannableStringBuilder builder = new SpannableStringBuilder(s);

            future = executorService.submit(() -> {
                try {
                    editor.preRender(builder, result -> {
                        final TextField et = editText;
                        if (et != null) {
                            // TODO YR view.post
                            /*    et.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        if (key == generator) {
                                            final TextField et = editText;
                                            if (et != null) {
                                                selfChange = true;
                                                try {
                                                    result.dispatchTo(editText.getText());
                                                } finally {
                                                    selfChange = false;
                                                }
                                            }
                                        }
                                    }
                                });*/
                        }
                    });
                } catch (final Throwable t) {
                    final TextField et = editText;
                    if (et != null) {
                        // propagate exception to main thread
                        // TODO YR view.post
                      /*  et.post(new Runnable() {
                            @Override
                            public void run() {
                                throw new RuntimeException(t);
                            }
                        });*/
                    }
                }
            });
        }

        @Override
        public void onTextUpdated(String s, int i, int i1, int i2) {

        }
    }
}
